//This is dumbed down - the original system which had that nasty bug in it is available (if you want it) from the SVN repository revision 183
//
//  SmartGroup.m
//  Groups
//
//  Created by Mathieu on 1/08/06.
//  Copyright 2006 __MyCompanyName__. All rights reserved.
//

#import "SmartGroup.h"


@implementation SmartGroup

/** 
Called from awakeFromInsert and awakeFromFetch. Here we register for the 
notification for changes to the managed object context, in order to be able 
to refresh the smart group when the object graph changes.
*/

- (void)commonAwake {
	
    words = nil;
    [[NSNotificationCenter defaultCenter] addObserver:self 
											 selector:@selector(refresh:) 
												 name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]];	
}

/**
Returns the name of the image used to represent this smart group instance
 in the view.  If no value has been specified, the default image name is
 returned.
 */

- (NSString *)groupImageName {
	if(groupImageName == nil){
		if ([self isThisGroupTheLibrary]){
			groupImageName = @"image_group_shared";
		}else{
			groupImageName = @"image_group_smart";
		}
	}
return groupImageName;
}

/**
Retuns an attributed string with the group name, annotated with the image
 for the group at the beginning of th string using a text attachment.  We 
 cache the image per group so as to not reload the image each time.
 */

- (NSAttributedString *)nameWithImage {
	
    NSString *tmpValue;
    NSMutableAttributedString *result;
    NSImage *groupImage = nil;
    
    // check the cache first... 
    if (cachedNameWithImage != nil) {
        return cachedNameWithImage;
    }
    
    // get the name part of the string
    tmpValue = [self valueForKey:@"name"];
    tmpValue = (tmpValue == nil) ? @"" : tmpValue;
    
    // start with a mutablestring with the name (padding a space at beginning)
    result = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@",tmpValue]];
    
    groupImage = [NSImage imageNamed:[self groupImageName]];
    [groupImage setScalesWhenResized:YES];
    [groupImage setSize:NSMakeSize(14, 14)];
    
    if (groupImage != nil) {
		
        NSFileWrapper *wrapper = nil;
        NSTextAttachment *attachment = nil;
        NSAttributedString *icon = nil;
		
        // need a filewrapper to create an NSTextAttachment
        wrapper = [[NSFileWrapper alloc] init];
		
        // set the icon (this is what'll show up in attributed strings)
        [wrapper setIcon:groupImage];
        
        // you need an attachment to create the attributed string as an RTFd
        attachment = [[NSTextAttachment alloc] initWithFileWrapper:wrapper];
        
        // finally, the attributed string for the icon
        icon = [NSAttributedString attributedStringWithAttachment:attachment];
        [result insertAttributedString:icon atIndex:0];
		
        // cleanup
        [wrapper release];
        [attachment release];	
    }
    
    // set and return the result
    cachedNameWithImage = result;    
    return result;
}

/**
Mutator to set the name of a group.  We remove the image when the name of 
 the group is being edited (since that cannot be changed by the user), so
 here we simply call the normal Core Data methods to change the name and 
 then reset the cached image and name string.
 */

- (void)setNameWithImage:(NSString *)nameWithImage {
	
    [self willChangeValueForKey: @"nameWithImage"];
	
    [self setName:nameWithImage];
    [cachedNameWithImage release];
    cachedNameWithImage = nil;
    
    [self didChangeValueForKey: @"nameWithImage"];
    
}

- (void)setName:(NSString *)value  {
	
    [self willChangeValueForKey: @"name"];
    [self setPrimitiveValue: value forKey: @"name"];
    [self didChangeValueForKey: @"name"];
}

/** 
Overridden awakeFromInsertion method, used to call the commonAwake 
implementation (to register for the notification of changes to the 
				predicate.)  We also ensure the predicate is something valid here.
*/

- (void)awakeFromInsert  {
	
    // awake from insert
    [super awakeFromInsert];
    [self commonAwake];
    
    // create an initial predicate
   // [self setPredicate: [NSPredicate predicateWithValue: YES]];
}


/** 
Overridden awakeFromFetch method, used to call the commonAwake 
implementation (to register for the notification of changes to the 
				predicate.)
*/


- (void)awakeFromFetch  {
	
    [super awakeFromFetch];
    [self commonAwake];
}

/** 
Overridden didTurnIntoFault method.  In addition to releasing the array of 
words, the fetch request, and the predicate, we need to unregister for the 
notifications from the managed object context before dealloc-ing.
*/

- (void)didTurnIntoFault {
	
    [[NSNotificationCenter defaultCenter] removeObserver: self 
													name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]];
	
    [words release], words = nil;
    [fetchRequest release], fetchRequest = nil;
    [predicate release], predicate = nil;
    
    [super didTurnIntoFault];
}

//
///**
//Accessor for the fetch request for the SmartGroup.  The fetch
// request is used to fetch all of the matching objects for the predicate for 
// the SmartGroup.  The fetch request is only created once per object (since 
//the entity will not change), though the predicate for the request can 
// change as needed.
// */
//
- (NSFetchRequest *)fetchRequest  {
	
    if ( fetchRequest == nil ) {
		
        // create the fetch request for the words
        fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity: [NSEntityDescription entityForName:@"Word" inManagedObjectContext:[self managedObjectContext]]];
		
        // set the affected stores
        id store = [[self objectID] persistentStore];
        if (store != nil) {
            [fetchRequest setAffectedStores:[NSArray arrayWithObject:store]];
        }
		
    }
    
    return fetchRequest;
}


//takes string Extra predicate and attaches it as a predicate to the smart group's existing predicate
-(void)setExtraPredicate:(NSString *)extraPredicate{
	[self setValue:extraPredicate forKey:@"extraPredicateString"];
	//attach the constant Language Binding predicate to the newpredicate string
//	NSString *languageName = [[self valueForKey:@"language"] valueForKey:@"name"];
//	NSString *constantPredicateAndNewPredicateString = [NSString stringWithFormat:@"language.name like[c] '%@' %@", languageName, extraPredicate];
//	NSPredicate *newPredicate = [NSPredicate predicateWithFormat:constantPredicateAndNewPredicateString];
//	[self setPredicate:newPredicate];
}

-(NSPredicate *)smartGroupFilterPredicate{
	NSPredicate *predicateForThisGroup = [NSPredicate predicateWithFormat:[self valueForKey:@"extraPredicateString"]];
	return predicateForThisGroup;
}


/** 
Accesor for the array of words for the SmartGroup.  This implementation 
returns the objects matching the specified predicate using the cached fetch 
request.  An empty set (not nil) is returned if there are no objects to be 
found OR if an error was encountered with the fetch.
*/

//this is where the bindings ask for the set of objects. 
- (NSSet *)words {
	if(words == nil){
		NSError *err;
		NSFetchRequest *simpleRequest = [[NSFetchRequest alloc] init];
		NSPredicate *simplePred = [NSPredicate predicateWithFormat:@"language.name like[c] %@", [[self valueForKey:@"language"] valueForKey:@"name"]];
		[simpleRequest setPredicate:simplePred];
		[simpleRequest setEntity:[NSEntityDescription entityForName:@"Word" inManagedObjectContext:[[NSApp delegate] managedObjectContext]]];
		NSArray *results = [[[NSApp delegate] managedObjectContext] executeFetchRequest:simpleRequest error:&err];
		words = [[NSSet alloc] initWithArray:results];
		wordCount = [words count];
	}
	return words;
}
/** 
Words for smart groups aren't really settable. Ensure that nothing tries 
to mutate recipes by KVC.
*/

- (void)setWords:(NSSet *)newWords  {
    // noop   
}
/**
Method to refresh the content of the smart group.  Here we simply note the
 contents of the "words" array is going to change, and then release the
 array.  A new one will be lazily created as necessary. But I think I need to force it to create one.
 */

- (void)refresh {
	[self willChangeValueForKey:@"words"];
	[words release], words = nil;   
	wordCountCache = -1;
	[self didChangeValueForKey:@"words"];
}


/**
Method to refresh the SmartGroup object.  This method is invoked either when 
 the predicate changes OR when object change notifications are received from 
 the context.  Since a change to a predicate is immediately pushed into the 
 fetch request, all we need do here is clear the set of recipes so it will 
 be re-created on the next access.
 */

- (void)refresh:(NSNotification *)notification {
	
    // Performance and Infinite loop avoidance:  Only refresh if the 
    // updated/deleted/inserted objects include Words (the entity of the 
    // [self fetchRequest]) We don't want to re-fetch words if unrelated 
    // objects (for example, other smart groups) change.
	
	NSEnumerator *enumerator;
	id object;
	BOOL refresh = NO;
	
	NSEntityDescription *entity = [[self fetchRequest] entity];
	
	NSSet *updated = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
	NSSet *inserted = [[notification userInfo] objectForKey:NSInsertedObjectsKey];
	NSSet *deleted = [[notification userInfo] objectForKey:NSDeletedObjectsKey];
	
	enumerator = [updated objectEnumerator];	
	while ((refresh == NO) && (object = [enumerator nextObject])) {
		if ([object entity] == entity) {
			refresh = YES;	
		}
	}
	
	enumerator = [inserted objectEnumerator];	
	while ((refresh == NO) && (object = [enumerator nextObject])) {
		if ([object entity] == entity) {
			refresh = YES;	
		}
	}
	
	enumerator = [deleted objectEnumerator];	
	while ((refresh == NO) && (object = [enumerator nextObject])) {
		if ([object entity] == entity) {
			refresh = YES;	
		}
	}
	
    if ( (refresh == NO) && (([updated count] == 0) && ([inserted count] == 0) && ([deleted count]==0))) {
        refresh = YES;
    }
    
	// OPTIMIZATION TIP:  We could collect all of the Word objects from the 
    // inserted and updated NSSets and add them to an array. Filter the array 
    // using [self predicate]. Only if the filtered array is non-empty would we 
    // need to update our words set (we could simply add the objects to the 
    // set)
	
	// OPTIMIZATION TIP: We could remove the objects of the deleted set 
    // directly from our words set ([words minusSet:deleted]).
	
    if (refresh) {
		[self refresh];
    }
}


- (bool)isManualGroup {
    return NO;
}

-(bool)isThisGroupTheLibrary{
	NSNumber *yes = [NSNumber numberWithBool:YES];
	if([[self valueForKey:@"isLibrary"] isEqualToNumber:yes]){
		return YES;
	}else{
		return NO;
	}
}


- (BOOL)canEditPredicate {
    return YES;
}

- (int)wordCount{
	return wordCount;
}

#pragma mark Archiving Support
//- (void)encodeWithCoder:(NSCoder *)coder
//{
//	[coder encodeObject:[self valueForKey:@"createdOn"] forKey:@"WCreatedOn"];
//	[coder encodeObject:[self valueForKey:@"isDef"] forKey:@"WIsDef"];
//	[coder encodeObject:[self valueForKey:@"known"] forKey:@"WKnown"];
//	[coder encodeObject:[self valueForKey:@"lastQuizzed"] forKey:@"WLastQuizzed"];
//	[coder encodeObject:[self valueForKey:@"mainDef"] forKey:@"WMainDef"];
//	[coder encodeObject:[self valueForKey:@"maybeKnown"] forKey:@"WMaybeKnown"];
//	[coder encodeObject:[self valueForKey:@"secondaryDef"] forKey:@"WSecondaryDef"];
//	[coder encodeObject:[self valueForKey:@"textBlobData"] forKey:@"WTextBlobData"];
//	[coder encodeObject:[self valueForKey:@"timesEncountered"] forKey:@"WTimesEncountered"];
//	[coder encodeObject:[self valueForKey:@"word"] forKey:@"WWord"];
//	[coder encodeObject:[self valueForKey:@"wordType"] forKey:@"WWordType"];
//    return;
//}
//
//
//- (id)initWithCoder:(NSCoder *)coder
//{
//	WordMO *word = [NSEntityDescription insertNewObjectForEntityForName:@"Word" inManagedObjectContext:[[NSApp delegate] managedObjectContext]];
//	[word setValue:[coder decodeObjectForKey:@"WCreatedOn"] forKey:@"createdOn"];
//	[word setValue:[coder decodeObjectForKey:@"WIsDef"] forKey:@"isDef"];
//	[word setValue:[coder decodeObjectForKey:@"WKnown"] forKey:@"known"];    
//	[word setValue:[coder decodeObjectForKey:@"WLastQuizzed"] forKey:@"lastQuizzed"];    
//	[word setValue:[coder decodeObjectForKey:@"WMainDef"] forKey:@"mainDef"];    
//	[word setValue:[coder decodeObjectForKey:@"WMaybeKnown"] forKey:@"maybeKnown"];    
//	[word setValue:[coder decodeObjectForKey:@"WSecondaryDef"] forKey:@"secondaryDef"];    
//	[word setValue:[coder decodeObjectForKey:@"WTextBlobData"] forKey:@"textBlobData"];    
//	[word setValue:[coder decodeObjectForKey:@"WTimesEncountered"] forKey:@"timesEncountered"];    
//	[word setValue:[coder decodeObjectForKey:@"WWord"] forKey:@"word"];    
//	[word setValue:[coder decodeObjectForKey:@"WWordType"] forKey:@"wordType"];    
//	return word;
//}

@end
